package cn.regexp.mybatisinterceptordemo.interceptor;

import cn.regexp.mybatisinterceptordemo.exception.BusinessException;
import cn.regexp.mybatisinterceptordemo.utils.CommonUtil;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.util.JdbcConstants;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.List;


@Slf4j
@Setter
@Accessors(chain = true)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SqlInterceptor implements Interceptor {

    @SneakyThrows
    public static void main(String[] args) {

        // String sql = "select * from user where id = (select id from user where name = 1)";
        //
        // SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql);
        // System.out.println(sqlStatement.getChildren());
        //
        //
        // System.out.println(sqlStatement.getDbType());
        //
        //
        // List<SQLStatement> list = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
        // System.out.println(list.size());


        String sql = "insert into user(id, name, age, tenant_id) value(?, ?, ?, ?)";
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql);
        SQLInsertStatement sqlInsertStatement = (SQLInsertStatement) sqlStatement;


        List<SQLExpr> columns = sqlInsertStatement.getColumns();
        boolean isExist = columns.contains(SQLUtils.toMySqlExpr("tenant_id"));
        if (isExist) {
            System.out.println("存在tenant_id");
        }

        sqlInsertStatement.addColumn(SQLUtils.toMySqlExpr("tenant_id"));

        for (SQLInsertStatement.ValuesClause clause : sqlInsertStatement.getValuesList()) {
            clause.addValue("10001");
        }


        System.out.println(sqlInsertStatement);

    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();

        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        String newSql = "";
        if (SqlCommandType.SELECT.equals(sqlCommandType)) {
            newSql = SQLUtils.addCondition(sql, "tenant_id = '" + CommonUtil.threadLocal.get() + "'",
                    SQLBinaryOperator.BooleanAnd, true, JdbcConstants.MYSQL);


        } else if (SqlCommandType.INSERT.equals(sqlCommandType)) {
            // 添加 tenant_id 字段
            newSql = addSetField(sql);
        } else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // 检查是否 set 中是否包含 tenant_id 字段
            checkSql(sql);

            // 添加条件
            newSql = SQLUtils.addCondition(sql, "tenant_id = '" + CommonUtil.threadLocal.get() + "'",
                    SQLBinaryOperator.BooleanAnd, true, JdbcConstants.MYSQL);
        } else if (SqlCommandType.DELETE.equals(sqlCommandType)) {
            // 添加条件
            newSql = SQLUtils.addCondition(sql, "tenant_id = '" + CommonUtil.threadLocal.get() + "'",
                    SQLBinaryOperator.BooleanAnd, true, JdbcConstants.MYSQL);
        } else {
            log.warn("sqlCommandType：{}，未知SQL类型", sqlCommandType);
        }

        // 通过反射修改 SQL 语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, newSql);

        return invocation.proceed();
    }

    private String addSetField(String sql) {

        // 解析 SQL 语句
        SQLInsertStatement sqlInsertStatement = (SQLInsertStatement) SQLUtils.parseSingleMysqlStatement(sql);
        // 获取插入列
        List<SQLExpr> columns = sqlInsertStatement.getColumns();
        // 是否存在租户 id
        boolean isExist = columns.contains(SQLUtils.toMySqlExpr("tenant_id"));
        if (isExist) {
            return sql;
        }

        // 添加租户 id
        sqlInsertStatement.addColumn(SQLUtils.toMySqlExpr("tenant_id"));
        // 添加租户 id 的值
        for (SQLInsertStatement.ValuesClause clause : sqlInsertStatement.getValuesList()) {
            clause.addValue(CommonUtil.threadLocal.get());
        }

        return sqlInsertStatement.toString();
    }

    private void checkSql(String sql) throws BusinessException {
        String columnName = "";

        // 解析 SQL 语句
        SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) SQLUtils.parseSingleMysqlStatement(sql);

        // 获取所有 set 字段
        List<SQLUpdateSetItem> setItems = sqlUpdateStatement.getItems();
        for (SQLUpdateSetItem setItem : setItems) {
            SQLExpr column = setItem.getColumn();

            if (column instanceof SQLPropertyExpr) {
                columnName = ((SQLPropertyExpr) column).getName();

            }

            if (column instanceof SQLIdentifierExpr) {
                columnName = ((SQLIdentifierExpr) column).getName();
            }

            if ("tenant_id".equals(columnName)) {
                throw new BusinessException("888888", "不允许修改tenant_id字段！");
            }
        }
    }

    @Test
    public void SQLUpdateStatement() {
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("update users u set u.name = '唐僧',age = 18 where u.id = 1 ");
        SQLUpdateStatement sqlUpdateStatement = ((SQLUpdateStatement) sqlStatement);
        List<SQLUpdateSetItem> setItems = sqlUpdateStatement.getItems();
        for (SQLUpdateSetItem setItem : setItems) {
            SQLExpr column = setItem.getColumn();
            if (column instanceof SQLPropertyExpr) {
                SQLPropertyExpr sqlPropertyExpr = ((SQLPropertyExpr) column);
                SQLExpr value = setItem.getValue();
                log.info("column:{},列owner:{},value:{}", sqlPropertyExpr.getName(), sqlPropertyExpr.getOwnernName(), value);
            }
            if (column instanceof SQLIdentifierExpr) {
                SQLExpr value = setItem.getValue();
                log.info("column:{},value:{}", column, value);
            }
        }
    }

    @Test
    public void SQLDeleteStatement2() {
        String sql = "delete from users where id = 1";
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql);
        SQLDeleteStatement sqlDeleteStatement = (SQLDeleteStatement) sqlStatement;
        SQLExpr where = sqlDeleteStatement.getWhere();
        SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) where;
//        DELETE FROM users
//        WHERE id = 2
        sqlBinaryOpExpr.addBeforeComment("tenant _id = 10001");
//        sqlBinaryOpExpr.setRight(SQLUtils.toSQLExpr("2"));
        System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));

        System.out.println(SQLUtils.addCondition(sql, "tenant_id = 10001",
                SQLBinaryOperator.BooleanAnd, true, JdbcConstants.MYSQL));
    }

}